﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Red.Controls.Shell;
using Red.Controls.Utils;
using Red.Launcher.Dialogs;
using Red.Launcher.Executor;
using Red.Launcher.Menus.Workspaces.Admin;
using Red.Launcher.Utilities;
using Dialog = Red.Launcher.Utilities.Dialog;
using P4 = Red.Core.Perforce;
using SourceControl = Red.Core.SourceControl;
using ViewModel = Red.Launcher.ViewModels.Workspaces;

namespace Red.Launcher
{
    public partial class WorkspacesView : UserControl
    {
        #region Private Data

        private ViewModel.Manager _workspaces = new ViewModel.Manager();

        Dialog.Manager _dialogs = new Dialog.Manager();

        #endregion Private Data

        #region Public Properties

        public static readonly DependencyProperty IsHelpOpenProperty = DependencyProperty.Register( "IsHelpOpen", typeof( bool ), typeof( WorkspacesView ) );
        public static readonly DependencyProperty HelpProperty = DependencyProperty.Register( "Help", typeof( string ), typeof( WorkspacesView ) );
        public static readonly DependencyProperty BusyWorkspacesCountProperty = DependencyProperty.Register( "BusyWorkspacesCount", typeof( int ), typeof( WorkspacesView ) );

        public ViewModel.Workspace ActiveWorkspace { get { return _workspaces.ActiveWorkspace; } }

        public bool IsHelpOpen
        {
            get { return (bool)GetValue( IsHelpOpenProperty ); }
            set { SetValue( IsHelpOpenProperty, value ); }
        }

        public string Help
        {
            get { return (string)GetValue( HelpProperty ); }
            set { SetValue( HelpProperty, value ); }
        }

        public int BusyWorkspacesCount
        {
            get { return (int)GetValue( BusyWorkspacesCountProperty ); }
            set { SetValue( BusyWorkspacesCountProperty, value ); }
        }

        public List<ViewModel.Workspace> BusyWorkspaces { get; private set; } = new List<ViewModel.Workspace>();

        #endregion Public Properties

        #region C-Tor

        partial void InitializeAnalytics();

        public WorkspacesView()
        {
            DataContext = _workspaces;
            InitializeComponent();

            _adminUsersButton.Click += _adminUsersButton_Click;
            _adminWorkspacesButton.Click += _adminWorkspacesButton_Click;
            _refreshWorkspacesButton.Click += _refreshWorkspacesButton_Click;

            _workspaces.PropertyChanged += _workspaces_PropertyChanged;
            _workspaces.OnFetchUserComplete += () =>
            {
                if ( OnUserChanged != null )
                {
                    _dialogs.User = _workspaces.User;
                    OnUserChanged( _workspaces.User );
                }
            };
            _workspaces.WorkspaceActive += _workspaces_WorkspaceActive;

            Loaded += WorkspacesView_Loaded;

            new CustomVariables.Perforce.ActiveWorkspace() { Value = "No Active Workspace selected" };
            new CustomVariables.Perforce.ActiveUsername() { Value = "No Active Workspace selected" };

            _workspaces.WorkspaceActive += () =>
            {
                new CustomVariables.Perforce.ActiveWorkspace() { Value = _workspaces.ActiveWorkspace?.PerforceName ?? "No Active Workspace selected" };
                new CustomVariables.Perforce.ActiveUsername() { Value = _workspaces.ActiveWorkspace?.Credentials.Name ?? "No Active Workspace selected" };
            };
        }

        #endregion C-Tor

        #region Private Methods

        private void SetWorkspaceBusy( ViewModel.Workspace workspace, bool busy, string message = null )
        {
            workspace.BusyMessage = message;
            workspace.IsBusy = busy;

            if ( busy )
            {
                System.Diagnostics.Debug.Assert( !BusyWorkspaces.Contains( workspace ) );
                BusyWorkspaces.Add( workspace );
            }
            else
            {
                BusyWorkspaces.Remove( workspace );
            }

            BusyWorkspacesCount = BusyWorkspaces.Count;
        }

        private async Task UpdateActiveWorkspace()
        {
            ViewModel.Workspace workspace = _workspaces.ActiveWorkspace;

            if ( workspace != null && !workspace.IsUpToDate )
            {
                bool updateRequested = RedMessageBox.Show( this, RedDialogType.YesNo, $"The specification for {ActiveWorkspace.DisplayName} has changed.\nWould you like to update your workspace now?", "Workspace out of date", RedMessageBox.Mode.Info );

                if ( updateRequested )
                {
                    await workspace.UpdateInPerforce();
                    await GetLatest( workspace, false );
                }
            }
        }

        private async Task InstallWorkspace( ViewModel.Workspace workspaceToInstall )
        {
            ViewModel.SpecificationWorkspace workspace = workspaceToInstall as ViewModel.SpecificationWorkspace;

            if ( workspace != null && !workspace.IsInstalled )
            {
                if ( workspace.IsBusy )
                    return;

                SetWorkspaceBusy( workspace, true, "Installing" );

                string path = Path.Combine( Settings.WorkspaceInstallationDirectory.ResolvedPath, workspaceToInstall.PerforceNameShort );

                DirectoryPicker.CalculateSpaceRequiredDelegate calculator = () =>
                {
                    P4.Mediator mediator = new P4.Mediator( Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

                    ViewModel.Database.P4ClientSpecification specification = workspace.WorkspaceInfo as ViewModel.Database.P4ClientSpecification;

                    if ( specification != null )
                    {
                        return mediator.GetSize( specification.Stream );
                    }

                    return 0;
                };

                string destination = DirectoryPicker.Show( this, $"Red Launcher will now checkout the contents of '{workspace.DisplayName}' to the following location:", "Create Workspace", path, calculateSizeRequired: calculator );

                bool isSelected = _workspacesDataGrid.SelectedItem == workspace;
                bool isActive = workspace.IsActive;

                if ( destination == null )
                {
                    workspace.IsActive = false;
                }
                else
                {
                    // Sanitise the input
                    destination = Path.GetFullPath( destination );

                    ViewModel.Workspace implementedWorkspace = await _workspaces.CreateWorkspaceInPerforce( workspace, destination );
                    if ( implementedWorkspace != null )
                    {
                        if ( isSelected )
                        {
                            _workspacesDataGrid.SelectedItem = implementedWorkspace;
                        }

                        implementedWorkspace.IsActive = isActive;
                    }
                    else
                    {
                        workspace.IsActive = false;
                    }
                }

                SetWorkspaceBusy( workspace, false );
            }
        }

        public class WorkspaceMoveException : Exception
        {
            public WorkspaceMoveException( string message ) : base( message )
            {
            }

            public WorkspaceMoveException( string message, Exception inner ) : base( message, inner )
            {
            }
        }

        private void MoveWorkspace( ViewModel.Workspace workspace, string target )
        {
            P4.Mediator mediator = new P4.Mediator( Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

            DirectoryInfo sourceInfo = new DirectoryInfo( mediator.GetDepotRoot() );
            DirectoryInfo targetInfo = new DirectoryInfo( target );

            try
            {
                targetInfo.Create();
            }
            catch ( IOException ex )
            {
                throw new WorkspaceMoveException( "Could not create target directory", ex );
            }

            try
            {
                // Perform the copy
                Files.Copy( sourceInfo, targetInfo );
            }
            catch( UnauthorizedAccessException ex )
            {
                throw new WorkspaceMoveException( $"There was a problem attempting to copy the workspace to location: {target}\n\nEither you do not have permission to edit this location, or another program is preventing Red Launcher from overwriting that location", ex );
            }

            if ( !mediator.ChangeWorkspaceRoot( workspace.PerforceName, targetInfo.FullName ) )
            {
                throw new WorkspaceMoveException( "Could not update Perforce with new workspace root" );
            }

            // Cleanup the old workspace
            sourceInfo.ClearAttributes();

            try
            {
                sourceInfo.Delete( true );
            }
            catch ( IOException ex )
            {
                // Probably couldn't delete the root directory (and it's empty)
                throw new WorkspaceMoveException( $"An error occurred trying to clean up old files at {sourceInfo}.\nIt should be safe to delete these manually", ex );
            }
        }

        private async Task MoveWorkspace( ViewModel.Workspace workspace )
        {
            if ( workspace.IsBusy )
                return;

            SetWorkspaceBusy( workspace, true, "Moving" );

            P4.Mediator mediator = new P4.Mediator( Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

            if ( !mediator.HasValidStream() )
            {
                await RedMessageBox.ShowAsync( this, RedDialogType.Ok, $"{workspace.DisplayName} appears to be invalid. Please check to make sure it has been configured properly by an admin", "Invalid workspace", RedMessageBox.Mode.Warning );
                return;
            }

            DirectoryPicker.CalculateSpaceRequiredDelegate calculator = () =>
            {
                return mediator.GetWorkspaceSize();
            };

            string defaultSuggestion = Path.Combine( Settings.WorkspaceInstallationDirectory.ResolvedPath, workspace.PerforceName );
            string directory = await DirectoryPicker.ShowAsync( this, $"Please select the location to which you will move the workspace '{workspace.DisplayName}':", "Move workspace", defaultSuggestion, calculator );

            string originalDirectory = mediator.GetDepotRoot();

            if ( directory != null )
            {
                bool isSubDirectory = await Task.Run( () => { return Core.IO.Path.Contains( originalDirectory, directory ); } );

                if ( Directory.Exists( originalDirectory ) && !isSubDirectory )
                {
                    try
                    {
                        await Task.Run( () => MoveWorkspace( workspace, directory ) );
                    }
                    catch ( WorkspaceMoveException ex )
                    {
                        await RedMessageBox.ShowAsync( this, RedDialogType.Ok, $"{ex.Message}\n\n{ex.InnerException?.Message ?? string.Empty}", "Error occurred moving workspace", RedMessageBox.Mode.Warning );
                    }
                }
            }

            SetWorkspaceBusy( workspace, false );
        }

        #endregion Private Methods

        #region Public Methods

        public async Task InitializeOnce()
        {
            if ( _workspaces.User == null )
            {
                await _workspaces.FetchUser( ( (App)App.Current ).User.UserPrincipalName );

                if ( _workspaces.User != null && _workspaces.Workspaces.Count == 0 )
                {
                    await _workspaces.FetchWorkspaces();
                }
            }

            InitializeAnalytics();
        }

        public async Task Reinitialize()
        {
            _workspaces.User = null;
            _workspaces.Workspaces.Clear();
            await InitializeOnce();
        }

        public async Task GetLatest( ViewModel.Workspace workspace, bool force )
        {
            if ( workspace.IsBusy )
                return;

            SetWorkspaceBusy( workspace, true, "Getting latest" );

            if ( force )
            {
                bool isUserSure = await RedMessageBox.ShowAsync( Window.GetWindow( this ), RedDialogType.OkCancel, "Are you sure you want to force get latest?\nThis is equivalent to downloading the entire repository from scratch!", "Are you absolutely sure?", RedMessageBox.Mode.Info );

                if ( !isUserSure )
                {
                    SetWorkspaceBusy( workspace, false );

                    return;
                }
            }

            P4.Mediator mediator = new P4.Mediator( Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

            SourceControl.Monitor monitor = new SourceControl.Monitor() { SourceControl = mediator };
            monitor.Error += GetLatest_Error;

            if ( await mediator.SignInCheckAndPrompt() )
            {
                string root = $"//{workspace.PerforceName}/...";

                await Task.Run
                (
                    () =>
                    {
                        mediator.GetLatestNoReturn( new List<string>() { root }, force );
                    }
                );
            }

            monitor.Error -= GetLatest_Error;
            monitor.Clear();
            monitor.SourceControl = null;

            SetWorkspaceBusy( workspace, false );
        }

        #endregion Public Methods

        #region RedMainWindow implementation

        public void WriteToSettings( UserSettings settings )
        {
            settings.ActiveWorkspace = _workspaces.ActiveWorkspaceUId;
        }

        public void LoadFromSettings( UserSettings settings )
        {
            _workspaces.ActiveWorkspaceUId = settings.ActiveWorkspace;
            _workspaces.AppConfig = settings.WorkspacesConfig;
        }

        #endregion

        #region Event Handlers

        private async void _workspaces_PropertyChanged( object sender, PropertyChangedEventArgs e )
        {
            if ( e.PropertyName == "ActiveWorkspace" )
            {
                OnActiveWorkspaceChanged?.Invoke();

                await InstallWorkspace( _workspaces.ActiveWorkspace );
            }
        }

        private void WorkspacesView_Loaded( object sender, RoutedEventArgs e )
        {
            Window parentWindow = Window.GetWindow( this );
            parentWindow.Closing += parentWindow_Closing;
            _dialogs.Owner = parentWindow;
        }

        private void _workspaces_WorkspaceActive()
        {
            Action showDialog = async () =>
            {
                // We need to call this function from outside the event handler, to avoid triggering the
                // "Cannot change ObservableCollection during a CollectionChanged event." InvalidOperationException
                await UpdateActiveWorkspace();
            };

            Dispatcher.BeginInvoke( DispatcherPriority.Normal, showDialog );
        }

        private void ExpandRow_Click( object sender, RoutedEventArgs e )
        {
            DataGridRow row = Helpers.FindVisualAncestorOfType<DataGridRow>( sender );
            row.DetailsVisibility = row.DetailsVisibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
        }

        void parentWindow_Closing( object sender, CancelEventArgs e )
        {
            _dialogs.CloseAll();
        }

        private async void _dialogs_AdminDialogClosed( Dialog.IEvents dialog, Dialog.CloseReason reason )
        {
            if ( reason == Dialog.CloseReason.Ok )
            {
                // Refresh
                await _workspaces.FetchWorkspaces();
            }
        }

        private void _adminUsersButton_Click( object sender, RoutedEventArgs e )
        {
            _dialogs.Show<UserListDialog>( _dialogs_AdminDialogClosed, true );
        }

        private void _adminWorkspacesButton_Click( object sender, RoutedEventArgs e )
        {
            _dialogs.Show<WorkspaceAdminDialog>( _dialogs_AdminDialogClosed, true, _workspaces.Mediator );
        }

        private async void _refreshWorkspacesButton_Click( object sender, RoutedEventArgs e )
        {
            if ( _workspaces.User == null )
            {
                await _workspaces.FetchUser( ( (App)App.Current ).User.UserPrincipalName );
            }

            await _workspaces.FetchWorkspaces();
        }

        private void SetActive_Clicked( object sender, RoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)_workspacesDataGrid.SelectedItem;

            workspace.IsActive = true;
        }

        private async void GetLatest_Clicked( object sender, RoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)_workspacesDataGrid.SelectedItem;

            await GetLatest( workspace, false );
        }

        private async void GetLatest_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            await GetLatest( (ViewModel.Workspace)e.Parameter, false );
        }

        private async void ForceGetLatest_Clicked( object sender, RoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)_workspacesDataGrid.SelectedItem;

            await GetLatest( workspace, true );
        }

        private async void GetLatest_Error( string message )
        {
            await RedMessageBox.ShowAsync( this, RedDialogType.Ok, message, "Error getting latest from Perforce", RedMessageBox.Mode.Warning );
        }

        private async void Install_Clicked( object sender, RoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)_workspacesDataGrid.SelectedItem;

            await InstallWorkspace( workspace );
        }

        private void BrowseFiles_Clicked( object sender, RoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)_workspacesDataGrid.SelectedItem;
            P4.Mediator mediator = new P4.Mediator( Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

            Run.ShellExecuteFilePath( mediator.GetDepotRoot(), this );
        }

        private void GetLatest_CanExecute( object sender, CanExecuteRoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)e.Parameter;
            e.CanExecute = !( workspace?.IsBusy ?? true ) && ( workspace?.IsInstalled ?? false );
        }

        private async void Move_Clicked( object sender, RoutedEventArgs e )
        {
            ViewModel.Workspace workspace = (ViewModel.Workspace)_workspacesDataGrid.SelectedItem;
            await MoveWorkspace( workspace );
        }

        #endregion Event Handlers

        #region Events

        public delegate void ActiveWorkspaceChanged();
        public event ActiveWorkspaceChanged OnActiveWorkspaceChanged;

        public delegate void UserChanged( ViewModel.Database.User user );
        public event UserChanged OnUserChanged;

        #endregion Events
    }
}
